#include <miniprof.h>
#include <miniprofpub.h>

#include <malloc.h>
#include <string.h>

#include <sys/time.h>
#include <sys/types.h>

#include <stdbool.h>

#include "log.h"

static bool broked = false; // something isn't right!

static struct miniprof_profiledfunction fregs[MAXFUNCTIONREGS];

static int fregstop = 1; // Index 0 is going to be something special

//Stack of entry times for each function call.
static struct timeval timestack[MAXCALLDEPTH];
static int timestacktop = 0;

//Stack of functions that have called start
//but have not yet called stop.
static int callstack[MAXCALLDEPTH];
static int callstacktop = 0;

int miniprof_start(char* functionname, int regindex){

	if(broked){
		return;
	}

	if(regindex == 0){ // function is being entered for the first time.
		regindex = fregstop;
		fregs[regindex].name = functionname;

		// allocate enough memory for caller stats for this function
		// and hook it up.
		fregs[regindex].callers = (int*) malloc(MAXFUNCTIONREGS * sizeof(int));
		if(fregs[regindex].callers == NULL){
			LOGD("ABORT!");
			broked = true;
			return;
		}
		//memset(fregs[regindex].callers, 0, MAXFUNCTIONREGS * sizeof(int));
		fregstop++;
	}

	// bump the overall calls
	fregs[regindex].calls++;
	// bump the per function calls
	//fregs[regindex].callers[callstack[callstacktop]]++;

	struct timeval timenow;
	gettimeofday(&timenow, NULL);
	timestack[timestacktop] = timenow;
	timestacktop++;

	callstack[callstacktop] = regindex;
	callstacktop++;

	return regindex;
}

void miniprof_stop(int regindex){
	// stop profiling.

	if(broked){
		return;
	}

	if(timestacktop == 0 || callstacktop == 0 || fregstop == 1){
		broked = true;
		LOGD("abort abort!!!");
		return;
	}

	if(timestacktop + 1 == MAXCALLDEPTH || callstacktop + 1 == MAXCALLDEPTH){
		broked = true;
		LOGD("max call depth overflow");
		return;
	}


	timestacktop--;
	callstacktop--;

	if(callstack[callstacktop] != regindex){
		LOGD("the function that just exited wasn't at the top of the call stack!");
		broked = true;
		return;
	}

	struct timeval timenow, timediff, timetemp;
	gettimeofday(&timenow, NULL);
	timersub(&timenow, &timestack[timestacktop], &timediff);


	if(!timerisset(&fregs[regindex].ttmin)){
		fregs[regindex].ttmin = timediff;
	}
	else if(timercmp(&timediff, &fregs[regindex].ttmin, <)){
		fregs[regindex].ttmin = timediff;
	}

	if(!timerisset(&fregs[regindex].ttmax)){
			fregs[regindex].ttmax = timediff;
	}
	else if(timercmp(&timediff, &fregs[regindex].ttmin, >)){
		fregs[regindex].ttmax = timediff;
	}


	/*
		//
		if(timetaken > fregs[regindex].ttmax){
				fregs[regindex].ttmax = timetaken;
		}

		//
		if(fregs[regindex].ttmean != 0){
			fregs[regindex].ttmean = (fregs[regindex].ttmean + timetaken) / 2;
		}
		else {
			fregs[regindex].ttmean = timetaken;
		}
	*/

	timeradd(&fregs[regindex].tttotal, &timediff, &timetemp);
	fregs[regindex].tttotal = timetemp;

}

// So you don't have to get the whole struct when you just want the
// name.
char* miniprof_funcname(int regindex){

	if(broked){
		return NULL;
	}

	return fregs[regindex].name;
}

void miniprof_reset(int regindex){

	if(broked){
		return;
	}


	fregs[regindex].calls = 0;
	timerclear(&fregs[regindex].ttmin);
	timerclear(&fregs[regindex].ttmean);
	timerclear(&fregs[regindex].ttmax);
	timerclear(&fregs[regindex].tttotal);

	//memset(fregs[regindex].callers, 0, MAXFUNCTIONREGS * sizeof(int));
}

int miniprof_numofregs(){

	if(broked){
		return 0;
	}


	return fregstop;
}

struct miniprof_profiledfunction miniprof_collect(int regindex){

	if(broked){
		return;
	}

	return fregs[regindex];
}
